﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PlayerTest.Player.Event;

// DShow
using DirectShowLib;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Threading;
using System.IO;
using PlayerTest.Player;
using System.Runtime.InteropServices.ComTypes;
using System.Diagnostics;
using System.Windows.Media;
using System.Globalization;
using System.Windows.Media.Imaging;

using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace PlayerTest
{
    public delegate void SetMessageDelegate(String msg);
    class CorePlayer : System.Windows.Forms.IMessageFilter
    {
        #region 변수 선언
        IGraphBuilder pGraphBuilder = null;     // DShow용 그래프와 컨트롤 선언
        IMediaControl pMediaControl = null;     // 영상 제어
        IMediaEvent pMediaEvent = null;         // 이벤트 제어 - 자막
        IMediaEventEx pMediaEventEx = null;
        IMediaPosition pMediaPosition = null;   // 영상 재생 시간
        IVideoWindow pVideoWindow = null;       // Panel 재생
        IBasicAudio pBasicAudio = null;         // 음성 조절

        // 음성 녹음
        ICaptureGraphBuilder2 pCaptureGraphBuilder2 = null;
        IGraphBuilder pGraphBuilder_capture = null;
        IMediaControl pCaptureMediaControl = null;
        IBaseFilter pMuxFilter = null;
        IBaseFilter pSourceFilter = null;
        IFileSinkFilter pDestFilter = null;


        // Filters
        IBaseFilter pFilterSAMICCParser = null;
        IBaseFilter pFilterVideoMixingRenderer9 = null;
        IBaseFilter pFilterInternalScriptCommandRenderer = null;

        // VMR Overlay Mixer
        IVMRFilterConfig9 pVMR_FilterConfig9 = null;
        IVMRWindowlessControl9 pVMR_WindowlessControl9 = null;

        // WinForm Panel
        private System.Windows.Forms.Panel panel;
        private IntPtr handle = IntPtr.Zero;

        // Video Size
        private int v_width;
        private int v_height;
        private int v_ar_width;
        private int v_ar_height;

        // 동영상 파일 크기와 재생 시간
        int v_filesize;
        int v_playtime;
        double db_playtime;

        // 재생 파일 경로
        String path_Video = "";
        String path_Subtitle = "";

        // Etc
        private const int DxMagicNumber = -759872593;
        Microsoft.DirectX.Direct3D.Device device;
        System.Windows.Forms.WebBrowser web = new System.Windows.Forms.WebBrowser();

        // 자막 렌더링 스레드
        private Thread testThread;
        private SynchronizationContext _threadContext;
        private Surface surface = null;

        public enum SubType { KRCC, ENCC, UNITED, NONE }
        private SubType visibleSubtitleType = SubType.KRCC;
        public SubType SubtitleType
        {
            get { return visibleSubtitleType; }
            set
            {
                visibleSubtitleType = value;

                // 자막 새로고침
                ThreadPool.QueueUserWorkItem(new WaitCallback(MakeSubtitleBitmap), null);
            }
        }
        #endregion

        #region Event Sending
        public event PlayerEventHandler EventHandler;
        /// <summary>
        /// Player에게 이벤트 전송
        /// </summary>
        /// <param name="arg"></param>
        private void SendPlayerEvent(PlayerEventArgs arg)
        {
            // 연결된 모든 함수에 이벤트 전송
            PlayerEventHandler handler = EventHandler;
            if (handler != null) handler(this, arg);
        }
        #endregion

        #region 초기화 / 해제
        public CorePlayer(System.Windows.Forms.Panel _panel)
        {
            panel = _panel;
            InitializeDirect3D();

            _threadContext = SynchronizationContext.Current;
            Trace.WriteLine(string.Format("[Thread] Initializing. [ID: {0}]", Thread.CurrentThread.ManagedThreadId));

            //testThread = new Thread(new ThreadStart(test));
            //testThread.Start();
            //ThreadPool.QueueUserWorkItem(new WaitCallback(MakeSubtitleBitmap), _threadContext);
            section_start = -1;
            section_end = -1;
        }
        ~CorePlayer()
        {
            Stop();
        }
        /// <summary>
        /// D3D 초기화
        /// </summary>
        private void InitializeDirect3D()
        {
            Microsoft.DirectX.Direct3D.PresentParameters present_params = new Microsoft.DirectX.Direct3D.PresentParameters();
            present_params.Windowed = true;
            present_params.SwapEffect = Microsoft.DirectX.Direct3D.SwapEffect.Discard;

            // 디바이스 생성
            device = new Microsoft.DirectX.Direct3D.Device(
                0,
                Microsoft.DirectX.Direct3D.DeviceType.Hardware,
                panel,
                Microsoft.DirectX.Direct3D.CreateFlags.SoftwareVertexProcessing | Microsoft.DirectX.Direct3D.CreateFlags.MultiThreaded,
                present_params);
        }

        public void SetWindowHandle(IntPtr _handle)
        {
            handle = _handle;
        }
        /*
        public void SetImage(System.Windows.Controls.Image _img)
        {
            imageControl = _img;
        }*/
        #endregion

        #region 자막 렌더링
        private object lockKRCC = new object();
        private object lockENCC = new object();
        /// <summary>
        /// Thread에서 동작할 함수
        /// </summary>
        /// <param name="o"></param>
        private void MakeSubtitleBitmap(object o)
        {
            string thistype = o as string;
            string subtitleKRCCText = subKRCC;
            string subtitleENCCText = subENCC;

            if (visibleSubtitleType == SubType.NONE)
                return;
            
            /*
            switch (thistype)
            {
                case "krcc":
                    subtitleText = subKRCC;
                    break;
                case "encc":
                    subtitleText = subENCC;
                    break;
                case SubType.UNITED:
                    // ??
                    subtitleText = subENCC;
                    break;
            }

            
                IStream interThreadStream;
                Guid IID_D3DSurface9 = typeof(Microsoft.DirectX.PrivateImplementationDetails.IDirect3DSurface9).GUID;
                CoMarshalInterThreadInterfaceInStream(ref IID_D3DSurface9, pFilterVideoMixingRenderer9, out interThreadStream);

                surface.UnmanagedComPointer;
                
                IStream interStream = o as IStream;
                Guid IID_IBaseFilter = typeof(IBaseFilter).GUID;
                object obj;
                IBaseFilter pVMR = null;
             * 
                CoGetInterfaceAndReleaseStream( interStream, ref IID_IBaseFilter, out obj );
                pVMR = obj as IBaseFilter;
             */

            int cx = 0, cy = 0;
            int hr = -1;
            System.Drawing.Bitmap bitmap = null;
            System.Drawing.Graphics g = null;

            Trace.WriteLine(string.Format("[Thread] Start Drawing. [ID: {0}]", Thread.CurrentThread.ManagedThreadId));

            try
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();

                #region 자막 텍스트를 이미지로 그려내기
                //Trace.WriteLine(string.Format("[Sub] Position : ({0},{1}) / ({2},{3})", v_width, v_height, panel.Width, panel.Height));

                bitmap = new System.Drawing.Bitmap(v_width, v_height);
                bitmap.MakeTransparent();

                g = System.Drawing.Graphics.FromImage(bitmap);
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

                System.Drawing.Brush br = System.Drawing.Brushes.Black;
                System.Drawing.Brush br_white = System.Drawing.Brushes.White;
                System.Drawing.Pen pen = new System.Drawing.Pen(br, 2.0f);

                // 자막 크기 자동으로
                int fontsize = (int)Math.Floor(v_width / 24.0f);

                // 자막 Path 넓이 얻기
                System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
                System.Drawing.FontFamily family = new System.Drawing.FontFamily("굴림체");
                Point origin = new Point(cx, cy);
                RectangleF rf_krcc = new RectangleF();
                RectangleF rf_encc = new RectangleF();
                
                switch (visibleSubtitleType)
                {
                    case SubType.KRCC:
                        path.AddString(subtitleKRCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                        rf_krcc = path.GetBounds();
                        path.Dispose();

                        path = new System.Drawing.Drawing2D.GraphicsPath();
                        cx = (int)((panel.Width - rf_krcc.Width) / 2.0f);
                        cy = (int)(panel.Height - rf_krcc.Height - (fontsize / 2));
                        origin = new Point(cx, cy);
                        path.AddString(subtitleKRCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                        break;

                    case SubType.ENCC:
                        path.AddString(subtitleENCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                        rf_encc = path.GetBounds();
                        path.Dispose();

                        path = new System.Drawing.Drawing2D.GraphicsPath();
                        cx = (int)((panel.Width - rf_encc.Width) / 2.0f);
                        cy = (int)(panel.Height - rf_encc.Height - (fontsize / 2));
                        origin = new Point(cx, cy);
                        path.AddString(subtitleENCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                        break;

                    case SubType.UNITED:
                        path.AddString(subtitleKRCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                        rf_krcc = path.GetBounds();
                        path.Dispose();

                        path = new System.Drawing.Drawing2D.GraphicsPath();
                        path.AddString(subtitleENCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                        rf_encc = path.GetBounds();
                        path.Dispose();

                        path = new System.Drawing.Drawing2D.GraphicsPath();
                        cx = (int)((panel.Width - rf_krcc.Width) / 2.0f);
                        cy = (int)(panel.Height - rf_krcc.Height - (fontsize / 2));
                        origin = new Point(cx, cy);
                        path.AddString(subtitleKRCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);

                        cx = (int)((panel.Width - rf_encc.Width) / 2.0f);
                        cy = (int)(panel.Height - rf_encc.Height - (fontsize*3));
                        origin = new Point(cx, cy);
                        path.AddString(subtitleENCCText, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                        break;
                }
                
                // 자막 위치 재설정
                

                Trace.WriteLine(string.Format("[RENDER] {0} x {1} / {2} x {3}", rf_encc.Width, rf_encc.Height, rf_krcc.Width, rf_krcc.Height));
                
                g.DrawPath(pen, path);
                g.FillPath(br_white, path);

                #endregion

                // DX를 사용하여 화면에 오버레이 렌더링
                surface = new Surface(device, bitmap, Microsoft.DirectX.Direct3D.Pool.SystemMemory);

                sw.Stop();
                Trace.WriteLine(string.Format("[CORE] Render subtitle : {0} ms", sw.ElapsedMilliseconds));
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.StackTrace);
            }
            finally
            {
                if (bitmap != null) bitmap.Dispose();
                if (g != null) g.Dispose();
            }

            _threadContext.Post(OnThreadPost, surface);
        }

        /// <summary>
        /// Thread로부터 이벤트를 받아 처리할 함수
        /// </summary>
        /// <param name="state"></param>
        private void OnThreadPost(object state)
        {
            try
            {
                IntPtr unmanagedSurface = surface.GetObjectByValue(DxMagicNumber);

                VMR9AlphaBitmap alphaBitmap = new VMR9AlphaBitmap();
                alphaBitmap.dwFlags = VMR9AlphaBitmapFlags.EntireDDS;
                alphaBitmap.rDest = new NormalizedRect(0f, 0f, (float)1F, (float)1F);
                alphaBitmap.pDDS = unmanagedSurface;
                alphaBitmap.fAlpha = 1f;
                //alphaBitmap.clrSrcKey = System.Drawing.Color.Black.ToArgb();
                //alphaBitmap.dwFlags |= VMR9AlphaBitmapFlags.SrcColorKey;

                IVMRMixerBitmap9 mixerBitmap = (IVMRMixerBitmap9)pFilterVideoMixingRenderer9;
                int result = mixerBitmap.SetAlphaBitmap(ref alphaBitmap);

                surface.Dispose();
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.StackTrace);
            }

            //Trace.WriteLine(string.Format("[Thread-M] Message Received. [ID: {0}]", Thread.CurrentThread.ManagedThreadId));
        }

        private string subKRCC = "";
        private string subENCC = "";
        public string KRCC
        {
            get { return subKRCC; }
            set { lock( lockKRCC ){ subKRCC = value; } }
        }
        public string ENCC
        {
            get { return subENCC; }
            set { lock (lockENCC) { subENCC = value; } }
        }
        public void SetKRCC(object text)
        {
            if (text != null)
            {
                KRCC = text as string;
                if( visibleSubtitleType==SubType.KRCC || visibleSubtitleType==SubType.UNITED )
                    ThreadPool.QueueUserWorkItem(new WaitCallback(MakeSubtitleBitmap), "krcc");
            }
        }
        public void SetENCC(object text)
        {
            if (text != null)
            {
                ENCC = text as string;
                if (visibleSubtitleType == SubType.ENCC || visibleSubtitleType == SubType.UNITED)
                    ThreadPool.QueueUserWorkItem(new WaitCallback(MakeSubtitleBitmap), "encc");
            }
        }

        #region Using GDI+


        /// <summary>
        /// 비트맵을 VMR과 DirectX Surface를 사용하여 화면에 오버레이 출력
        /// </summary>
        /// <param name="bitmap"></param>
        /// <returns></returns>
        private int BlendSubtitleText(Bitmap bitmap)
        {
            int result = 0;
            #region DX를 사용하여 화면에 오버레이 렌더링

            try
            {

                Surface surface = new Surface(device, bitmap, Microsoft.DirectX.Direct3D.Pool.SystemMemory);
                IntPtr unmanagedSurface = surface.GetObjectByValue(DxMagicNumber);

                VMR9AlphaBitmap alphaBitmap = new VMR9AlphaBitmap();
                alphaBitmap.dwFlags = VMR9AlphaBitmapFlags.EntireDDS;
                alphaBitmap.rDest = new NormalizedRect(0f, 0f, (float)1F, (float)1F);
                alphaBitmap.pDDS = unmanagedSurface;
                alphaBitmap.fAlpha = 1f;
                //alphaBitmap.clrSrcKey = System.Drawing.Color.Black.ToArgb();
                //alphaBitmap.dwFlags |= VMR9AlphaBitmapFlags.SrcColorKey;

                IVMRMixerBitmap9 mixerBitmap = (IVMRMixerBitmap9)pFilterVideoMixingRenderer9;
                result = mixerBitmap.SetAlphaBitmap(ref alphaBitmap);

                surface.Dispose();
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.StackTrace);
            }

            #endregion

            return result;
        }

        private int BlendApplicationText(string text)
        {
            int cx = 0, cy = 0;
            int hr = -1;
            System.Drawing.Bitmap bitmap = null;
            System.Drawing.Graphics g = null;

            try
            {
                #region 자막 텍스트를 이미지로 그려내기
                //Trace.WriteLine(string.Format("[Sub] Position : ({0},{1}) / ({2},{3})", v_width, v_height, panel.Width, panel.Height));

                bitmap = new System.Drawing.Bitmap(v_width, v_height);
                bitmap.MakeTransparent();

                g = System.Drawing.Graphics.FromImage(bitmap);
                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

                System.Drawing.Brush br = System.Drawing.Brushes.Black;
                System.Drawing.Brush br_white = System.Drawing.Brushes.White;
                System.Drawing.Pen pen = new System.Drawing.Pen(br, 2.0f);

                // 자막 크기 자동으로
                int fontsize = (int)Math.Floor(v_height / 20.0f);

                // 자막 위치
                cy = panel.Height - (int)fontsize - 20;
                /*
                Font ft = new Font("굴림체", 24.0f);
                Trace.WriteLine(string.Format("[Sub] Font size : {0}", fontsize));
                
                //g.DrawString(text, ft, br, cx - 1, cy - 1);
                //g.DrawString(text, ft, br, cx - 1, cy + 1);
                //g.DrawString(text, ft, br, cx + 1, cy - 1);
                //g.DrawString(text, ft, br, cx + 1, cy + 1);
                //g.DrawString(text, ft, br_white, cx, cy);
                */

                System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
                System.Drawing.FontFamily family = new System.Drawing.FontFamily("굴림체");
                Point origin = new Point(cx, cy);
                path.AddString(text, family, (int)FontStyle.Bold, fontsize, origin, StringFormat.GenericDefault);
                g.DrawPath(pen, path);
                g.FillPath(br_white, path);

                #endregion

                #region Using Visual
                /*
                DrawingVisual dv = new DrawingVisual();
                DrawingContext dc = dv.RenderOpen();
                FormattedText subText = new FormattedText(
                    text,
                    CultureInfo.GetCultureInfo("ko-kr"),
                    System.Windows.FlowDirection.LeftToRight,
                    new Typeface("굴림체"),
                    32.0,
                    System.Windows.Media.Brushes.White, null, TextFormattingMode.Display);
                subText.SetFontSize(32.0f * (96.0 / 72.0));
                subText.SetFontWeight(System.Windows.FontWeights.Bold);
                subText.SetFontStretch(System.Windows.FontStretches.Expanded);
                
                Geometry outline = subText.BuildGeometry(new System.Windows.Point(0,0));
                //subText.BuildHighlightGeometry(new System.Windows.Point(0, 0));
                System.Windows.Media.Brush br_black = System.Windows.Media.Brushes.Black;//DarkSlateGray;
                System.Windows.Media.Brush br_yellow = System.Windows.Media.Brushes.Yellow;
                System.Windows.Media.Pen pen = new System.Windows.Media.Pen(br_black, 4.0f);
                //RenderOptions.SetEdgeMode(outline, EdgeMode.Aliased);
                RenderOptions.SetEdgeMode(dv, EdgeMode.Aliased);

                dc.DrawGeometry(br_black, pen, outline);
                dc.DrawText(subText, new System.Windows.Point(0, 0));
                

                dc.Close();
                
                RenderTargetBitmap rtb = new RenderTargetBitmap(v_width, v_height, 96, 96, PixelFormats.Pbgra32);
                rtb.Render(dv);

                BmpBitmapEncoder bmpEncoder = new BmpBitmapEncoder();
                bmpEncoder.Frames.Add(BitmapFrame.Create(rtb));
                System.IO.MemoryStream ms = new MemoryStream();
                bmpEncoder.Save(ms);

                bitmap = new Bitmap(ms);
                bitmap.MakeTransparent();
                */
                #endregion

                Stopwatch sw = new Stopwatch();
                sw.Start();
                // DX를 사용하여 화면에 오버레이 렌더링
                hr = BlendSubtitleText(bitmap);

                sw.Stop();
                Trace.WriteLine(string.Format("Print image : {0} ms", sw.ElapsedMilliseconds));
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.StackTrace);
            }
            finally
            {
                if (bitmap != null) bitmap.Dispose();
                if (g != null) g.Dispose();
            }
            return hr;
        }

        [DllImport("gdi32.dll")]
        private static extern bool DeleteObject(IntPtr hObject);

        [DllImport("ole32.dll")]
        public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);

        [DllImport("ole32.dll")]
        static extern int CoMarshalInterThreadInterfaceInStream([In] ref Guid riid,
           [MarshalAs(UnmanagedType.IUnknown)] object pUnk, out IStream ppStm);

        [DllImport("ole32.dll")]
        static extern int CoGetInterfaceAndReleaseStream(IStream pStm, [In] ref
            Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);


        #endregion
        #endregion

        #region 기본 동작
        public bool Open(string path)
        {
            path_Video = path;

            // 파일 크기 정보
            FileInfo fi = new FileInfo(path_Video);
            v_filesize = (int)fi.Length;


            // 영상과 같은 위치에 자막 파일이 있는지 찾는다.
            String sub = fi.FullName.Substring(0, fi.FullName.LastIndexOf("."));
            sub += ".smi";
            FileInfo fi_sub = new FileInfo(sub);
            if (fi_sub != null)
            {
                SetSubtitle(sub);
            }

            // 이전에 재생중인 파일이 있었다면 중지 후 재생
            if (pGraphBuilder != null) CloseGraph();

            if (InitGraph())
            {
                InitCaptureGraph();
                // 동영상 파일 정보를 가지고 video ID를 구한다.
                PlayerEventArgs arg = new PlayerEventArgs(PlayerEventArgs.eventtype.VIDEOINFO);
                arg.PlayTime = v_playtime;
                arg.FileSize = v_filesize;
                SendPlayerEvent(arg);

                pMediaControl.Run();    // 재생!
                SendPlayerEvent(new PlayerEventArgs(PlayerEventArgs.eventtype.PLAYSTART));
            }
            else
            {
                System.Windows.Forms.MessageBox.Show("재생할 수 없는 파일입니다!");
                return false;
            }

            return true;
        }

        public void Resize()
        {
            if (pMediaControl != null)
            {
                v_width = panel.Width;
                v_height = panel.Height;

                pVMR_WindowlessControl9.SetVideoPosition(null, new DsRect(0, 0, panel.Width, panel.Height));
                //pVMR_WindowlessControl9.Get(out v_width, out v_height, out v_ar_width, out v_ar_height);

                // 자막 다시그리기
            }
        }

        public void Pause()
        {
            FilterState pfs = new FilterState();
            if (pMediaControl != null)
            {
                // 재생중이라면 일시정지하고
                // 일시정지 중이라면 재생한다.
                pMediaControl.GetState(-1, out pfs);

                if (pfs == FilterState.Running)
                {
                    pMediaControl.Pause();
                    SendPlayerEvent(new PlayerEventArgs(PlayerEventArgs.eventtype.PLAYPAUSE));
                }
                else if (pfs == FilterState.Paused)
                {
                    pMediaControl.Run();
                    SendPlayerEvent(new PlayerEventArgs(PlayerEventArgs.eventtype.PLAYSTART));
                }
            }
        }

        public void Stop()
        {
            if (pMediaControl != null)
            {
                pMediaControl.Stop();
                SendPlayerEvent(new PlayerEventArgs(PlayerEventArgs.eventtype.PLAYSTOP));

                // 필터그래프 연결 해제
                CloseGraph();
                RecordEnd();
            }
            if (repeatThread != null)
                repeatThread.Abort();
        }

        /// <summary>
        /// 영상을 재생한다.
        /// </summary>
        public void Play()
        {
            pMediaControl.Run();
            SendPlayerEvent(new PlayerEventArgs(PlayerEventArgs.eventtype.PLAYSTART));
        }
        
        /// <summary>
        /// 영상 위치 조절
        /// </summary>
        /// <param name="pos"></param>
        public void SetPosition(double pos)
        {
            if (pMediaPosition != null)
            {
                pMediaPosition.put_CurrentPosition(pos);
            }
        }

        public void SetVolume(double vol)
        {
            // 볼륨값을 dB로 변환
            double lfPower10 = Math.Pow(10, -10);
            double lfVolume = (1 - lfPower10) * vol + lfPower10;
            lfVolume = 10 * Math.Log10(lfVolume);
            lfVolume *= 100;

            pBasicAudio.put_Volume((int)lfVolume);

            Trace.WriteLine(string.Format("Volume changed. {0}", vol));

            //PlayerEventArgs arg = new PlayerEventArgs(PlayerEventArgs.eventtype.VOLUME);
            //arg.Volume = vol;
            //SendPlayerEvent(arg);
        }

        private void SetSubtitle(String path)
        {
            path_Subtitle = path;
        }

        public double CurrentPosition
        {
            get
            {
                if (pGraphBuilder != null)
                {
                    double pos;
                    pMediaPosition.get_CurrentPosition(out pos);
                    return pos;
                }
                return -1;
            }
        }
        #endregion

        #region 프레임 캡쳐
        private string recentThumb = null;
        public string RecentThumbFile
        {
            get { return recentThumb; }
        }
        public void FrameCapture( int videoID )
        {
            DateTime origin = new DateTime(1970,1,1);
            recentThumb = string.Format("{0}_{1}.png", videoID,
                (DateTime.UtcNow - origin).TotalMilliseconds / 1000L);
            IntPtr imgData;
            if (pVMR_WindowlessControl9 != null)
            {
                pVMR_WindowlessControl9.GetCurrentImage(out imgData);
                SaveToPng(imgData, recentThumb);
                Marshal.FreeCoTaskMem(imgData);
            }
        }
        private void SaveToPng(IntPtr Source, string filename)
        {
            if (Directory.Exists("thumbs") == false)
                Directory.CreateDirectory("thumbs");
            string path = ".\\thumbs\\" + filename;
            BitmapInfoHeader bmi = new BitmapInfoHeader();
            bmi = (BitmapInfoHeader)Marshal.PtrToStructure(Source, typeof(BitmapInfoHeader));

            int width = (int)bmi.Width;
            int height = (int)bmi.Height;

            int stride = width * (bmi.BitCount / 8);
            System.Drawing.Imaging.PixelFormat pixelFormat = System.Drawing.Imaging.PixelFormat.Format24bppRgb;

            switch (bmi.BitCount)
            {
                case 32:
                    pixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppRgb;
                    break;
            }

            IntPtr Scan0 = (IntPtr)(((int)Source) + (bmi.Size));
            Bitmap img = new Bitmap(width, height, stride, pixelFormat, Scan0);
            img.RotateFlip(RotateFlipType.RotateNoneFlipY);
            System.Drawing.Image thumb = img.GetThumbnailImage(64, 64, null, IntPtr.Zero);
            //thumb.Save(path, System.Drawing.Imaging.ImageFormat.Png);
            img.Save(path, System.Drawing.Imaging.ImageFormat.Png);
            thumb.Dispose();
            img.Dispose();
        }
        #endregion

        #region 반복구간

        // 동영상 반복 구간
        private bool isRepeat = false;
        private double section_start;
        private double section_end;
        public double SectionStartTime
        {
            get { return section_start; }
        }
        public double SectionEndTime
        {
            get { return section_end; }
        }
        public double SectionStart()
        {
            double pos = 0;
            if (pMediaPosition != null)
            {
                pMediaPosition.get_CurrentPosition(out section_start);
                pos = section_start;
            }
            return pos;
        }
        public double SectionEnd()
        {
            double pos = 0;
            if (pMediaPosition != null)
            {
                pMediaPosition.get_CurrentPosition(out section_end);
                pos = section_end;
            }
            return pos;
        }

        private Thread repeatThread;
        public void RepeatStart(double start, double end)
        {
            if (pMediaPosition != null)
            {
                section_start = start;
                section_end = end;
                if (repeatThread != null)
                {
                    if (repeatThread.ThreadState != System.Threading.ThreadState.Stopped)
                        repeatThread.Abort();
                }
                // 현재 재생위치 시작점으로 이동
                pMediaPosition.put_CurrentPosition(section_start);
                
                // 자막 새로고침
                PlayerEventArgs arg = new PlayerEventArgs(PlayerEventArgs.eventtype.REPEAT);
                SendPlayerEvent(arg);

                // 반복 모니터링 스레드 시작
                repeatThread = new Thread(new ThreadStart(Monitor_Repeat));
                repeatThread.Start();
                /*
                if (section_start >= 0 && section_start < section_end)
                {
                    isRepeat = true;

                    Trace.WriteLine(string.Format("[Repeat] {0} ~ {1}", section_start, section_end));

                    pMediaPosition.put_CurrentPosition(section_start);
                    pMediaPosition.put_StopTime(section_end);
                }
                */
            }
        }

        private void Repeat_Begin(object o)
        {
            if (pMediaPosition != null)
            {
                pMediaPosition.put_CurrentPosition(section_start);

                // 자막 새로고침
                //ThreadPool.QueueUserWorkItem(new WaitCallback(MakeSubtitleBitmap), null);
                PlayerEventArgs arg = new PlayerEventArgs(PlayerEventArgs.eventtype.REPEAT);
                SendPlayerEvent(arg);
            }
        }

        private void Monitor_Repeat()
        {
            while( true )
            {
                while (CurrentPosition < section_end)
                    Thread.Sleep(100);

                _threadContext.Post(Repeat_Begin, null);
                Thread.Sleep(100);
            }
        }

        public void RepeatEnd()
        {
            if (repeatThread != null)
            {
                if (repeatThread.ThreadState != System.Threading.ThreadState.Stopped)
                    repeatThread.Abort();
            }
            /*
            if (pMediaPosition != null)
            {
                Trace.WriteLine("[Repeat] End !");
                isRepeat = false;
                pMediaPosition.put_StopTime(db_playtime);
            }*/
        }
        public void RepeatRelease()
        {
            if (repeatThread != null)
            {
                repeatThread.Abort();
                repeatThread = null;
            }
        }
        #endregion

        #region 이벤트 처리
        /// <summary>
        /// 메시지를 중간에 필터링하여 자막을 처리합니다.
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        public bool PreFilterMessage(ref System.Windows.Forms.Message m)
        {
            if (m.Msg == 0x401)
            {
                EventCode evCode;
                IntPtr lParam1, lParam2;
                String type;
                String text;

                while (pMediaEvent.GetEvent(out evCode, out lParam1, out lParam2, 100) == 0)
                {
                    //Trace.WriteLine(string.Format("[Message] {0}", evCode));
                    switch( evCode )
                    {
                        case EventCode.OleEvent:
                            type = System.Runtime.InteropServices.Marshal.PtrToStringAuto(lParam1);
                            text = System.Runtime.InteropServices.Marshal.PtrToStringAuto(lParam2);
                            //Trace.WriteLine(text);
                            text = text.Replace("<br>", "\n");
                            text = text.Replace("\n\n", "\n");
                            text = text.Substring(text.LastIndexOf(">") + 1);
                            //text = text.Replace("\n", "<br>");

                            //Trace.WriteLine(string.Format("[Thread] Queue Drawing. [ID: {0}]", Thread.CurrentThread.ManagedThreadId));
                            ThreadPool.QueueUserWorkItem(new WaitCallback(MakeSubtitleBitmap), text);

                            //BlendApplicationText(text);


                            /*
                            string style = "margin-left:0; margin-right:0; margin-bottom:0; margin-top:0;  color:white; background-color:black;";
                            string caption = text;
                            caption = string.Format("<html><body  Style=\"{1}\"><table border=0 width=100% height=100%><tr><Td valign=bottom>{0}</td></tr></table></body></html>", caption, style);
                            web.ScriptErrorsSuppressed = false;
                            web.ScrollBarsEnabled = false;
                            web.Width = panel.Width/2;
                            web.Height = panel.Height/2;
                            web.DocumentText = caption;

                            while (web.ReadyState != System.Windows.Forms.WebBrowserReadyState.Complete)
                                System.Windows.Forms.Application.DoEvents();

                            using (Bitmap bitmap = new System.Drawing.Bitmap(web.Width, web.Height))
                            {
                                web.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, web.Width, web.Height));
                                BlendSubtitleText(bitmap);
                            
                            }
                            */
                            break;

                        case EventCode.Complete:
                            if (isRepeat)
                            {
                                //pMediaPosition.put_CurrentPosition(section_start);
                            }
                            break;
                    }
                    pMediaEvent.FreeEventParams(evCode, lParam1, lParam2);
                    if (evCode == EventCode.UserAbort || evCode == EventCode.ErrorAbort)
                        break;
                }
                return true;
            }
            return false;
        }
        #endregion

        #region 음성 녹음
        public void RecordStart()
        {
            if (pCaptureGraphBuilder2 == null)
            {
                Trace.WriteLine("음성 녹음 시작!");
                InitCaptureGraph();
            }
        }
        public void RecordEnd()
        {
            if (pCaptureGraphBuilder2 != null)
            {
                Trace.WriteLine("음성 녹음 종료!");
                CloseCaptureGraph();
            }
        }
        private bool InitCaptureGraph()
        {
            // AVI
            //0xe436eb88, 0x524f, 0x11ce, 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70)
            // WAVE
            //0xe436eb8b, 0x524f, 0x11ce, 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70)
            int hr = 0;
            Guid gCapture = new Guid("E436EB88-524F-11CE-9F53-0020AF0BA770");   // MEDIASUBTYPE_Avi
            //Guid gCapture = new Guid("E436EB8B-524F-11CE-9F53-0020AF0BA770");   // MEDIASUBTYPE_Wave
            Guid gWM_ASF_Writer = new Guid("187463A0-5BB7-11D3-ACBE-0080C75E246E");

            // 음성 녹음
            pCaptureGraphBuilder2 = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();
            pCaptureGraphBuilder2.SetOutputFileName(gCapture, "D:\\test.avi", out pMuxFilter, out pDestFilter);
            pCaptureGraphBuilder2.GetFiltergraph(out pGraphBuilder_capture);

            FindCaptureDevice(out pSourceFilter);
            hr = pGraphBuilder_capture.AddFilter(pSourceFilter, "Audio Capture");
            if (hr < 0)
            {
                Trace.WriteLine(string.Format("그래프에 캡쳐 필터를 추가할 수 없습니다!!  hr=0x{0:X}", hr));
                Marshal.ReleaseComObject(pSourceFilter);
                return false;
            }
            /*
            hr = pGraphBuilder_capture.AddFilter(pMuxFilter, "Audio Capture");
            if (hr < 0)
            {
                Trace.WriteLine(string.Format("그래프에 MUX 필터를 추가할 수 없습니다!!  hr=0x{0:X}", hr));
                Marshal.ReleaseComObject(pSourceFilter);
                Marshal.ReleaseComObject(pMuxFilter);
                return false;
            }

            IConfigAsfWriter asfconfig = (IConfigAsfWriter2)pMuxFilter;
            DirectShowLib.IServiceProvider pServiceProvider = (DirectShowLib.IServiceProvider)pMuxFilter;
            */
            //asfconfig.ConfigureFilterUsingProfileGuid();

            pCaptureGraphBuilder2.RenderStream(
                null,           // Pin category
                null,           // Media Type
                pSourceFilter,  // Capture Filter
                null,           // Compression Filter
                pMuxFilter);    // Multiplexer of renderer filter

            pCaptureMediaControl = (IMediaControl)pGraphBuilder_capture;
            pCaptureMediaControl.Run();

            return true;
        }
        private void CloseCaptureGraph()
        {
            Marshal.ReleaseComObject(pSourceFilter);
            Marshal.ReleaseComObject(pGraphBuilder_capture);
            Marshal.ReleaseComObject(pCaptureGraphBuilder2);
            Marshal.ReleaseComObject(pCaptureMediaControl);

            pSourceFilter = null;
            pGraphBuilder_capture = null;
            pCaptureGraphBuilder2 = null;
            pCaptureMediaControl = null;
        }

        private int FindCaptureDevice(out IBaseFilter ppSrcFilter)
        {

            int hr = 0;
            object filterObject = null;
            IntPtr cFetched = IntPtr.Zero;
            IBindCtx bindCtx = null;

            ppSrcFilter = null;

            //0x33d9a762, 0x90c8, 0x11d0, 0xbd, 0x43, 0x0, 0xa0, 0xc9, 0x11, 0xce, 0x86)
            Guid CLSID_AudioInputDeviceCategory = new Guid("33D9A762-90C8-11D0-BD43-00A0C911CE86");
            Guid IID_IBaseFilter = typeof(IBaseFilter).GUID;

            CreateBindCtx(0, out bindCtx);

            foreach (DsDevice ds in DsDevice.GetDevicesOfCat(FilterCategory.AudioInputDevice))
            {
                ds.Mon.BindToObject(bindCtx, null, ref IID_IBaseFilter, out filterObject);
            }
            ppSrcFilter = (IBaseFilter)filterObject;
            return hr;
        }

        #endregion

        #region 필터 그래프 처리

        private bool InitGraph()
        {
            int renderResult = 0;
            int renderSubtitleResult = 0;
            bool result = false;

            // 새 필터 그래프 대응
            pGraphBuilder = (IGraphBuilder)new FilterGraph();
            // 필터 그래프에 컨트롤 붙이기
            pMediaControl = (IMediaControl)pGraphBuilder;
            // 필터 그래프에 이벤트 붙이기
            pMediaEvent = (IMediaEvent)pGraphBuilder;
            pMediaEventEx = (IMediaEventEx)pGraphBuilder;

            // 동영상 길이
            pMediaPosition = (IMediaPosition)pGraphBuilder;
            pVideoWindow = (IVideoWindow)pGraphBuilder;

            // 소리 제어
            pBasicAudio = (IBasicAudio)pGraphBuilder;

            #region 프레임 캡쳐 설정
            // SampleGrabber를 접속하는 포맷 지정
            // 화면 출력 직전에 샘플을 얻어온다.
            /*
            am_media_type.majorType = MediaType.Video;
            am_media_type.subType = MediaSubType.RGB24;
            am_media_type.formatType = FormatType.VideoInfo;
            pSampleGrabber.SetMediaType(am_media_type);

            // 필터 추가
            int grabber_res = pGraphBuilder.AddFilter(pSampleGrabberFilter, "Sample Grabber");
             */
            #endregion

            #region 오버레이 믹서

            // 그래프에 필터 추가

            Guid gFilterSAMICCParser = new Guid("33FACFE0-A9BE-11D0-A520-00A0D10129C0");
            Guid gFilterInternalScriptCommandRenderer = new Guid("48025243-2D39-11CE-875D-00608CB78066");
            Guid gFilterVideoMixingRenderer9 = new Guid("51B4ABF3-748F-4E3B-A276-C828330E926A");

            Type tFilterSAMICCParser = Type.GetTypeFromCLSID(gFilterSAMICCParser);
            Type tFilterInternalScriptCommandRenderer = Type.GetTypeFromCLSID(gFilterInternalScriptCommandRenderer);
            Type tFilterVideoMixingRenderer9 = Type.GetTypeFromCLSID(gFilterVideoMixingRenderer9);

            pFilterSAMICCParser = new SAMIParser() as IBaseFilter;
            pFilterVideoMixingRenderer9 = (IBaseFilter)new VideoMixingRenderer9();
            pFilterInternalScriptCommandRenderer = new InternalScriptCommandRenderer() as IBaseFilter;

            // 연결 전 VMR 설정
            pVMR_FilterConfig9 = (IVMRFilterConfig9)pFilterVideoMixingRenderer9;
            pVMR_FilterConfig9.SetNumberOfStreams(1);
            pVMR_FilterConfig9.SetRenderingMode(VMR9Mode.Windowless);

            pVMR_WindowlessControl9 = pVMR_FilterConfig9 as IVMRWindowlessControl9;
            pVMR_WindowlessControl9.SetVideoClippingWindow(panel.Handle);
            pVMR_WindowlessControl9.SetAspectRatioMode(VMR9AspectRatioMode.LetterBox);
            pVMR_WindowlessControl9.SetVideoPosition(null, new DsRect(0, 0, (int)panel.Width, (int)panel.Height));

            // 필터 추가
            pGraphBuilder.AddFilter(pFilterVideoMixingRenderer9, "Video Mixing Renderer 9");

            // 동영상 널 렌더링
            //renderResult = pGraphBuilder.RenderFile(path_Video, null);
            renderResult = pMediaControl.RenderFile(path_Video);

            // 필터 추가
            pGraphBuilder.AddFilter(pFilterSAMICCParser, "SAMI (CC) Parser");
            pGraphBuilder.AddFilter(pFilterInternalScriptCommandRenderer, "Internal Script Command Renderer");

            // 자막 파일 널 렌더링
            //renderSubtitleResult = pGraphBuilder.RenderFile(path_Subtitle, null);

            #endregion

            #region 프레임 캡쳐
            // 동영상 정보 얻기
            // Render 수행 이후에 해야한다.
            /*
            int tt = pSampleGrabber.GetConnectedMediaType(am_media_type);
            VideoInfoHeader pVideoInfoHeader = (VideoInfoHeader)Marshal.PtrToStructure(am_media_type.formatPtr, typeof(VideoInfoHeader));

            string str = string.Format("[Frame] sample size : {0}", am_media_type.sampleSize);
            Trace.WriteLine(str);
            DsUtils.FreeAMMediaType(am_media_type);

            // SetBufferSamples를 실행하지 않으면 버퍼로부터 데이터를 얻을 수 없다.
            pSampleGrabber.SetBufferSamples(true);
            */
            #endregion

            #region 재생 시간 / 화면 크기 얻기
            if (renderResult == 0)
            {
                // 재생 시간 얻기
                pMediaPosition.get_Duration(out db_playtime);
                v_playtime = (int)db_playtime;
                v_width = panel.Width;
                v_height = panel.Height;

                result = true;
            }
            #endregion

            #region 자막 처리

            // 자막을 이벤트 핸들링 방식으로 처리한다
            //System.Windows.Forms.Application.AddMessageFilter(this);
            //pMediaEventEx.SetNotifyWindow(handle, 0x401, IntPtr.Zero);

            // 자막 구성 조회
            /*
            if (pFilterSAMICCParser != null)
            {
                IAMStreamSelect pStrm = (IAMStreamSelect)pFilterSAMICCParser;
                if (pStrm != null)
                {

                    int dwStreams = 0;
                    pStrm.Count(out dwStreams);
                    pStrm.Enable(1, AMStreamSelectEnableFlags.EnableAll);
                    pStrm.Enable(0, AMStreamSelectEnableFlags.EnableAll);
                    String msg;
                    msg = String.Format("Stream Count: {0}", dwStreams);
                    Trace.WriteLine(msg);
                    
                    for (int index = 0; index < dwStreams; index++)
                    {
                        string wszName;
                        AMMediaType mtype;
                        AMStreamSelectInfoFlags dwFlags;
                        int plcid;
                        int pdwGroup;
                        object obj1, obj2;
                        int hr = pStrm.Info(index, out mtype, out dwFlags, out plcid, out pdwGroup, out  wszName, out obj1, out obj2);
                        if (hr == 0)
                        {
                            msg = String.Format("{0}", wszName);
                            Trace.WriteLine(msg);
                        }
                    }
                }
            }
            */
            #endregion

            return result;
        }

        private void CloseGraph()
        {
            // 그래프와 컨트롤 해제
            Marshal.ReleaseComObject(pGraphBuilder);
            Marshal.ReleaseComObject(pMediaControl);
            Marshal.ReleaseComObject(pMediaEvent);
            Marshal.ReleaseComObject(pMediaEventEx);
            Marshal.ReleaseComObject(pMediaPosition);
            Marshal.ReleaseComObject(pBasicAudio);

            Marshal.ReleaseComObject(pFilterSAMICCParser);
            Marshal.ReleaseComObject(pFilterInternalScriptCommandRenderer);
            Marshal.ReleaseComObject(pFilterVideoMixingRenderer9);

            pFilterSAMICCParser = null;
            pFilterInternalScriptCommandRenderer = null;
            pFilterVideoMixingRenderer9 = null;

            pGraphBuilder = null;
            pMediaControl = null;
            pMediaEvent = null;
            pMediaEventEx = null;
            pMediaPosition = null;
            pBasicAudio = null;
        }

        #endregion
    }
}
